﻿
using System;
using System.Linq;
using System.Reflection;
using Behavioral.Private;

namespace Behavioral
{
    public interface IInitializer : IInitializerMarker, IContextGetter, IContextSetter
    {
        void SetUp();
    }

    public interface IInitializer<TTarget> : IInitializerMarker, IContextGetter, IContextSetter
    {
        void SetUp(ref TTarget target);
    }

    public interface ITearDown : ITearDownMarker, IContextGetter, IContextSetter
    {
        void TearDown();
    }

    public interface ITearDown<TTarget> : ITearDownMarker, IContextGetter, IContextSetter
    {
        void TearDown(TTarget target);
    }

    public interface IErrorHandler : IErrorHandlerMarker, IContextGetter, IContextSetter
    {
        void OnError();
    }

    public interface IErrorHandler<TTarget> : IErrorHandlerMarker, IContextGetter, IContextSetter
    {
        void OnError(TTarget target);
    }

    namespace Private
    {
        public interface IInitializerMarker
        {

        }

        public interface ITearDownMarker
        {

        }

        public interface IErrorHandlerMarker
        {

        }

        internal class InitializationRunner : IContextGetterInternal, IContextSetterInternal
        {
            public void SetUp<TInitializer, TTarget>(TInitializer initializer, ref TTarget target)
                where TInitializer : IInitializerMarker
            {
                IInitializer initializerContextFree = initializer as IInitializer;
                if (initializerContextFree != null)
                {
                    initializerContextFree.SetUp();
                    return;
                }
                
                IInitializer<TTarget> initializerWithTarget = initializer as IInitializer<TTarget>;
                if (initializerWithTarget != null)
                {
                    initializerWithTarget.SetUp(ref target);
                    return;
                }

                if (!RunActionAsInitializer(initializer, target))
                {
                    ContextualRun(initializer, typeof (IInitializer<>), "SetUp");
                }
            }

            public void TearDown<TInitializer, TTarget>(TInitializer initializer, TTarget target)
                where TInitializer : ITearDownMarker
            {
                ITearDown tearDownContextFree = initializer as ITearDown;
                if (tearDownContextFree != null)
                {
                    tearDownContextFree.TearDown();
                    return;
                }
                ITearDown<TTarget> tearDownWithTarget = initializer as ITearDown<TTarget>;
                if (tearDownWithTarget != null)
                {
                    tearDownWithTarget.TearDown(target);
                    return;
                }
                
                ContextualRun(initializer, typeof(ITearDown<>), "TearDown");
            }

            public void OnError<TInitializer, TTarget>(TInitializer initializer, TTarget target)
            {
                IErrorHandler errorHandlerContextFree = initializer as IErrorHandler;
                if (errorHandlerContextFree != null)
                {
                    errorHandlerContextFree.OnError();
                    return;
                }
                IErrorHandler<TTarget> errorHandlerWithTarget = initializer as IErrorHandler<TTarget>;
                if (errorHandlerWithTarget != null)
                {
                    errorHandlerWithTarget.OnError(target);
                    return;
                }

                ContextualRun(initializer, typeof(IErrorHandler<>), "OnError");
            }

            private bool RunActionAsInitializer<TInitializer, TTarget>(TInitializer initializer, TTarget target)
            {
                Type actionType = initializer.GetType();
                Type actionInterface = actionType.GetInterfaces().SingleOrDefault(
                    type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IAction<,>));
                bool ranSuccessfully = false;
                if (actionInterface != null)
                {
                    actionType.InvokeMember("Execute",
                                            BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
                                            null, initializer, new object[] {target});
                    ranSuccessfully = true;
                }
                return ranSuccessfully;
            }

            private void ContextualRun<TInitializer>(TInitializer initializer, Type genericInterfaceType, string targetMethod)
            {
                Type initializerType = initializer.GetType().GetInterfaces().SingleOrDefault(type => type.IsGenericType && type.GetGenericTypeDefinition() == genericInterfaceType);
                if (initializerType != null)
                {
                    Type[] genericTypeArgs = initializerType.GetGenericArguments();
                    if (genericTypeArgs.Length == 1)
                    {
                        Type requiredTargetType = genericTypeArgs[0];
                        var contextValue = this.GetContext(requiredTargetType);
                        var invokeArgs = new[] { contextValue };
                        Type contextualInitializerType = genericInterfaceType.MakeGenericType(requiredTargetType);
                        contextualInitializerType.InvokeMember(targetMethod, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
                                                                   null, initializer, invokeArgs);
                        this.SetContext(requiredTargetType, invokeArgs[0]);
                    }
                }
            }
        }
    }
}
